package com.wsm.util.httpclient.register;

import com.wsm.util.httpclient.autoconfig.EnableHttpClient;
import com.wsm.util.httpclient.beanpostprocessor.HttpClientBeanPostProcessor;
import com.wsm.util.httpclient.filter.RequestTypeFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.core.type.filter.AbstractClassTestingTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author ：wangshimin
 * @date ：Created in 2021-02-27 下午 12:28
 * @description：
 * @version:
 */
@Slf4j
public class HttpClientBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    private static final String RESOURCE_PATTERN = "**/*.class";

    private static final String CLASS_PATTERN = ".class";
    /**
     * 生成的HttpUtil类
     */
    private static final Map<String, Class<?>> HTTP_UTIL_UNDERLYING_MAPPING = new HashMap<>();

    /**
     * @param importingClassMetadata
     * @param registry
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes annAttr = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableHttpClient.class.getName()));
        if (annAttr == null) {
            return;
        }
        String[] basePackages = annAttr.getStringArray("value");
        Class<?>[] baseClass = null;
        if (ObjectUtils.isEmpty(basePackages)) {
            basePackages = annAttr.getStringArray("basePackages");
        }
        if (ObjectUtils.isEmpty(basePackages)) {
            baseClass = annAttr.getClassArray("basePackageClasses");
        }
        if (ObjectUtils.isEmpty(basePackages) && baseClass == null) {
            basePackages = new String[]{ClassUtils.getPackageName(importingClassMetadata.getClassName())};
        }
        List<AbstractClassTestingTypeFilter> includeFilters = extractTypeFilters(annAttr.getClassArray("includeFilters"));
        //增加一个包含的过滤器,扫描到的类只要不是抽象的,接口,枚举,注解,及匿名类那么就算是符合的
        includeFilters.add(new RequestTypeFilter());
        List<AbstractClassTestingTypeFilter> excludeFilters = extractTypeFilters(annAttr.getClassArray("excludeFilters"));
        List<Class<?>> candidates = scanPackages(basePackages, baseClass, includeFilters, excludeFilters);
        if (candidates.isEmpty()) {
            log.warn("HttpClient-util:Scan specified base package {} no eligible base class was found", java.util.Arrays.toString(basePackages));
            return;
        }
        //注册HttpUtil后处理器
        registerBeanPostProcessor(registry);
        //保存HttpUtil
        registerBeanDefinitions(candidates);
    }

    /**
     * @param basePackages
     * @param includeFilters
     * @param excludeFilters
     * @return List<Class < ?>>
     */
    private List<Class<?>> scanPackages(String[] basePackages, Class<?>[] baseClass, List<AbstractClassTestingTypeFilter> includeFilters, List<AbstractClassTestingTypeFilter> excludeFilters) {
        List<Class<?>> candidates = new ArrayList<>();
        ResourceLoader resourceLoader = new DefaultResourceLoader();
        MetadataReaderFactory readerFactory = new SimpleMetadataReaderFactory(resourceLoader);
        ResourcePatternResolver resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        for (String basePackage : basePackages) {
            try {
                Resource[] resources = resourcePatternResolver.getResources(replaceDotByDelimiter(basePackage) + '/' + RESOURCE_PATTERN);
                for (Resource resource : resources) {
                    MetadataReader reader = readerFactory.getMetadataReader(resource);
                    if (isCandidateResource(reader, readerFactory, includeFilters, excludeFilters)) {
                        Class<?> candidateClass = transform(reader.getClassMetadata().getClassName());
                        if (candidateClass != null) {
                            candidates.add(candidateClass);
                            log.debug("HttpClient-util:扫描到符合要求HttpUtil基础类:{}" + candidateClass.getName());
                        }
                    }
                }
            } catch (IOException e) {
                log.error("HttpClient-util:Scan assignment HttpUtil base packages {} error", basePackage);
            }
        }
        //遍历类
        for (Class<?> aClass : baseClass) {
            try {
                MetadataReader reader = readerFactory.getMetadataReader(aClass.getName());
                if (isCandidateResource(reader, readerFactory, includeFilters, excludeFilters)) {
                    log.debug("HttpClient-util:Scan to meet the requirements of httputil basic class:{}" + aClass.getName());
                    candidates.add(aClass);
                }
            } catch (IOException e) {
                log.error("HttpClient-util:Scan assignment HttpUtil base class {} error", aClass.getName());
            }
        }
        return candidates;
    }

    /**
     * 注册HttpUtil Bean,
     * Bean的名称格式:
     * 当内部Bean的类名类似FloorServiceImpl时  "HttpUtilSpringProviderBean#FloorService"
     * 如果内部Bean实现了多个接口,请将实际的业务接口放在第一位
     *
     * @param internalClasses
     */
    private void registerBeanDefinitions(List<Class<?>> internalClasses) {
        for (Class<?> clazz : internalClasses) {
            if (HTTP_UTIL_UNDERLYING_MAPPING.containsValue(clazz)) {
                log.warn("HttpClient-util:重复扫描{}类,忽略重复注册", clazz.getName());
                continue;
            }
            HTTP_UTIL_UNDERLYING_MAPPING.put(generateBeanName(clazz), clazz);
        }
    }

    /**
     * 当接口重名时,后注册的HttpUtil Bean的名称后缀加上序号,从1开始,1代表第二个
     *
     * @param underlying
     * @return
     */
    private String generateBeanName(Class<?> underlying) {
        String beanName = ClassUtils.getShortNameAsProperty(underlying);
        if (HTTP_UTIL_UNDERLYING_MAPPING.containsKey(beanName)) {
            int order = 0;
            while (HTTP_UTIL_UNDERLYING_MAPPING.containsKey(beanName + order)) {
                order++;
            }
            beanName += order;
        }
        return beanName;
    }

    /**
     * 注册HttpUtil后处理器 已更换为import导入后置处理器
     *
     * @param registry
     */
    private void registerBeanPostProcessor(BeanDefinitionRegistry registry) {
        String beanName = ClassUtils.getShortNameAsProperty(HttpClientBeanPostProcessor.class);
        if (!registry.containsBeanDefinition(beanName)) {
            registry.registerBeanDefinition(beanName, new RootBeanDefinition(HttpClientBeanPostProcessor.class));
        }
    }

    /**
     * @param classes
     * @return
     */
    private String[] getPackagesFromClasses(Class[] classes) {
        if (ObjectUtils.isEmpty(classes)) {
            return null;
        }
        String[] basePackages = new String[classes.length];
        for (int i = 0; i < classes.length; i++) {
            basePackages[i] = classes[i].getName() + CLASS_PATTERN;
        }
        return basePackages;
    }

    /**
     * 用"/"替换包路径中"."
     *
     * @param path
     * @return
     */
    private String replaceDotByDelimiter(String path) {
        return StringUtils.replace(path, ".", "/");
    }

    /**
     * @param reader
     * @param readerFactory
     * @param includeFilters
     * @param excludeFilters
     * @return
     * @throws IOException
     */
    protected boolean isCandidateResource(MetadataReader reader, MetadataReaderFactory readerFactory, List<AbstractClassTestingTypeFilter> includeFilters,
                                          List<AbstractClassTestingTypeFilter> excludeFilters) throws IOException {
        for (TypeFilter tf : excludeFilters) {
            if (tf.match(reader, readerFactory)) {
                return false;
            }
        }
        for (TypeFilter tf : includeFilters) {
            if (tf.match(reader, readerFactory)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param className
     * @return
     */
    private Class<?> transform(String className) {
        Class<?> clazz = null;
        try {
            clazz = ClassUtils.forName(className, this.getClass().getClassLoader());
        } catch (ClassNotFoundException e) {
            log.info("HttpClient-util:The specified httpUtil base class was not found,{}", className);
        }
        return clazz;
    }

    /**
     * @param classes
     * @return
     */
    private List<AbstractClassTestingTypeFilter> extractTypeFilters(Class<?>[] classes) {
        List<AbstractClassTestingTypeFilter> typeFilters = new ArrayList<>();
        for (Class clazz : classes) {
            Assert.isAssignable(AbstractClassTestingTypeFilter.class, clazz,
                    "HttpClient-util:@EnableHttpClient custom filters must be extend AbstractClassTestingTypeFilter");
            AbstractClassTestingTypeFilter filter = BeanUtils.instantiateClass(clazz, AbstractClassTestingTypeFilter.class);
            typeFilters.add(filter);
        }
        return typeFilters;
    }

    public static Map<String, Class<?>> getAll() {
        return HTTP_UTIL_UNDERLYING_MAPPING;
    }

    public static void clean() {
        HTTP_UTIL_UNDERLYING_MAPPING.clear();
    }

}